home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / MacHacksBug / Python 1.5.2c1 / Extensions / Imaging / PIL / PngImagePlugin.py < prev    next >
Encoding:
Python Source  |  2000-06-23  |  9.7 KB  |  458 lines

  1. #
  2. # The Python Imaging Library.
  3. # $Id: PngImagePlugin.py,v 1.1.1.1 1998/08/18 13:07:53 sjoerd Exp $
  4. #
  5. # PNG support code
  6. #
  7. # See "PNG (Portable Network Graphics) Specification, version 1.0;
  8. # W3C Recommendation", 1996-10-01, Thomas Boutell (ed.).
  9. #
  10. # history:
  11. # 0.1    96-05-06 fl    Created (couldn't resist it)
  12. # 0.2    96-12-14 fl    Upgraded, added read and verify support
  13. #    96-12-15 fl    Separate PNG stream parser
  14. #     96-12-29 fl    Added write support, added getchunks
  15. # 0.3    96-12-30 fl    Eliminated circular references in decoder
  16. # 0.4    98-07-12 fl    Read/write 16-bit images as mode I
  17. #
  18. # Copyright (c) Secret Labs AB 1997-98.
  19. # Copyright (c) Fredrik Lundh 1996.
  20. #
  21. # See the README file for information on usage and redistribution.
  22. #
  23.  
  24. __version__ = "0.4"
  25.  
  26. import string
  27.  
  28. import Image, ImageFile, ImagePalette
  29.  
  30.  
  31. def i16(c):
  32.     return ord(c[1]) + (ord(c[0])<<8)
  33. def i32(c):
  34.     return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24)
  35.  
  36.  
  37. _MAGIC = "\211PNG\r\n\032\n"
  38.  
  39.  
  40. _MODES = {
  41.     # supported bits/color combinations, and corresponding modes/rawmodes
  42.     (1, 0): ("1", "1"),
  43.     (2, 0): ("L", "L;2"),
  44.     (4, 0): ("L", "L;4"),
  45.     (8, 0): ("L", "L"),
  46.     (16,0): ("I", "I;16B"),
  47.     (8, 2): ("RGB", "RGB"),
  48.     (16,2): ("RGB", "RGB;16B"),
  49.     (1, 3): ("P", "P;1"),
  50.     (2, 3): ("P", "P;2"),
  51.     (4, 3): ("P", "P;4"),
  52.     (8, 3): ("P", "P"),
  53.     (8, 4): ("RGBA", "LA"),
  54.     (16,4): ("RGBA", "LA;16B"),
  55.     (8, 6): ("RGBA", "RGBA"),
  56.     (16,6): ("RGBA", "RGBA;16B"),
  57. }
  58.  
  59.  
  60. # --------------------------------------------------------------------
  61. # Support classes.  Suitable for PNG and related formats like MNG etc.
  62.  
  63. class ChunkStream:
  64.  
  65.     def __init__(self, fp):
  66.  
  67.     self.fp = fp
  68.     self.queue = []
  69.  
  70.     if not hasattr(Image.core, "crc32"):
  71.         self.crc = self.crc_skip
  72.  
  73.     def read(self):
  74.     "Fetch a new chunk. Returns header information."
  75.  
  76.     if self.queue:
  77.         cid, pos, len = self.queue[-1]
  78.         del self.queue[-1]
  79.         self.fp.seek(pos)
  80.     else:
  81.         s = self.fp.read(8)
  82.         cid = s[4:]
  83.         pos = self.fp.tell()
  84.         len = i32(s)
  85.  
  86.     return cid, pos, len
  87.  
  88.     def close(self):
  89.     del self.queue
  90.     self.fp = None
  91.  
  92.     def push(self, cid, pos, len):
  93.  
  94.     self.queue.append((cid, pos, len))
  95.  
  96.     def call(self, cid, pos, len):
  97.     "Call the appropriate chunk handler"
  98.  
  99.     if Image.DEBUG:
  100.         print "STREAM", cid, pos, len
  101.     return getattr(self, "chunk_" + cid)(pos, len)
  102.  
  103.     def crc(self, cid, data):
  104.     "Read and verify checksum"
  105.  
  106.     crc1 = Image.core.crc32(data, Image.core.crc32(cid))
  107.     crc2 = i16(self.fp.read(2)), i16(self.fp.read(2))
  108.     if crc1 != crc2:
  109.         raise SyntaxError, "broken PNG file"\
  110.         "(bad header checksum in %s)" % cid
  111.  
  112.     def crc_skip(self, cid, data):
  113.     "Read checksum.  Used if the C module is not present"
  114.  
  115.     self.fp.read(4)
  116.  
  117.     def verify(self, endchunk = "IEND"):
  118.  
  119.     # Simple approach; just calculate checksum for all remaining
  120.     # blocks.  Must be called directly after open.
  121.  
  122.     cids = []
  123.  
  124.     while 1:
  125.         cid, pos, len = self.read()
  126.         if cid == endchunk:
  127.         break
  128.         self.crc(cid, self.fp.read(len))
  129.         cids.append(cid)
  130.  
  131.     return cids
  132.  
  133.  
  134. # --------------------------------------------------------------------
  135. # PNG image stream (IHDR/IEND)
  136.  
  137. class PngStream(ChunkStream):
  138.  
  139.     def __init__(self, fp):
  140.  
  141.     ChunkStream.__init__(self, fp)
  142.  
  143.     # local copies of Image attributes
  144.     self.im_info = {}
  145.     self.im_size = (0,0)
  146.     self.im_mode = None
  147.     self.im_tile = None
  148.     self.im_palette = None
  149.  
  150.     def chunk_IHDR(self, pos, len):
  151.     
  152.     # image header
  153.     s = self.fp.read(len)
  154.     self.im_size = i32(s), i32(s[4:])
  155.     try:
  156.         self.im_mode, self.im_rawmode = _MODES[(ord(s[8]), ord(s[9]))]
  157.     except:
  158.         pass
  159.     if ord(s[12]):
  160.         self.im_info["interlace"] = 1
  161.     if ord(s[11]):
  162.         raise SyntaxError, "unknown filter category"
  163.     return s
  164.  
  165.     def chunk_IDAT(self, pos, len):
  166.  
  167.     # image data
  168.     self.im_tile = [("zip", (0,0)+self.im_size, pos, self.im_rawmode)]
  169.     self.im_idat = len
  170.     raise EOFError
  171.  
  172.     def chunk_IEND(self, pos, len):
  173.  
  174.     # end of PNG image
  175.     raise EOFError
  176.  
  177.     def chunk_PLTE(self, pos, len):
  178.     
  179.     # palette
  180.     s = self.fp.read(len)
  181.     if self.im_mode == "P":
  182.         self.im_palette = "RGB", s
  183.     return s
  184.  
  185.     def chunk_gAMA(self, pos, len):
  186.  
  187.     # gamma setting
  188.     s = self.fp.read(len)
  189.     self.im_info["gamma"] = i32(s) / 100000.0
  190.     return s
  191.  
  192.     def chunk_tEXt(self, pos, len):
  193.  
  194.     # text
  195.     s = self.fp.read(len)
  196.     [k, v] = string.split(s, "\0")
  197.     self.im_info[k] = v
  198.     return s
  199.  
  200.  
  201. # --------------------------------------------------------------------
  202. # PNG reader
  203.  
  204. def _accept(prefix):
  205.     return prefix[:8] == _MAGIC
  206.  
  207. class PngImageFile(ImageFile.ImageFile):
  208.  
  209.     format = "PNG"
  210.     format_description = "Portable network graphics"
  211.  
  212.     def _open(self):
  213.  
  214.     if self.fp.read(8) != _MAGIC:
  215.         raise SyntaxError, "not a PNG file"
  216.  
  217.     #
  218.     # Parse headers up to the first IDAT chunk
  219.  
  220.     self.png = PngStream(self.fp)
  221.  
  222.     while 1:
  223.  
  224.         #
  225.         # get next chunk
  226.  
  227.         cid, pos, len = self.png.read()
  228.  
  229.         try:
  230.             s = self.png.call(cid, pos, len)
  231.         except EOFError:
  232.         break
  233.  
  234.         except AttributeError:
  235.         if Image.DEBUG:
  236.             print cid, pos, len, "(unknown)"
  237.         s = self.fp.read(len)
  238.  
  239.         self.png.crc(cid, s)
  240.  
  241.     #
  242.     # Copy relevant attributes from the PngStream.  An alternative
  243.     # would be to let the PngStream class modify these attributes
  244.     # directly, but that introduces circular references which are
  245.     # difficult to break no matter what happens in the decoders.
  246.     # (believe me, I've tried ;-)
  247.  
  248.     self.mode = self.png.im_mode
  249.     self.size = self.png.im_size
  250.     self.info = self.png.im_info
  251.     self.tile = self.png.im_tile
  252.     if self.png.im_palette:
  253.         rawmode, data = self.png.im_palette
  254.         self.palette = ImagePalette.raw(rawmode, data)
  255.  
  256.     self.__idat = len # used by load_read()
  257.  
  258.  
  259.     def verify(self):
  260.     "Verify PNG file"
  261.  
  262.     # back up to beginning of IDAT block
  263.     self.fp.seek(self.tile[0][2] - 8)
  264.  
  265.     self.png.verify()
  266.     self.png.close()
  267.  
  268.     self.fp = None
  269.  
  270.  
  271.     def load_read(self, bytes):
  272.     "Read more data from chunks (used by ImageFile.load)"
  273.  
  274.     while self.__idat == 0:
  275.         # end of chunk, skip forward to next one
  276.  
  277.         self.fp.read(4) # CRC
  278.  
  279.         cid, pos, len = self.png.read()
  280.  
  281.         if cid not in ["IDAT", "DDAT"]:
  282.         self.png.push(cid, pos, len)
  283.         return ""
  284.  
  285.         self.__idat = len # empty chunks are allowed
  286.  
  287.     # read more data from this chunk
  288.     if bytes <= 0:
  289.         bytes = self.__idat
  290.     else:
  291.         bytes = min(bytes, self.__idat)
  292.  
  293.     self.__idat = self.__idat - bytes
  294.  
  295.     return self.fp.read(bytes)
  296.  
  297. #
  298. # --------------------------------------------------------------------
  299. # PNG writer
  300.  
  301. def o16(i):
  302.     return chr(i>>8&255) + chr(i&255)
  303.  
  304. def o32(i):
  305.     return chr(i>>24&255) + chr(i>>16&255) + chr(i>>8&255) + chr(i&255)
  306.  
  307. _OUTMODES = {
  308.     # supported bits/color combinations, and corresponding modes/rawmodes
  309.     "1":   ("1", chr(1)+chr(0)),
  310.     "L;1": ("L;1", chr(1)+chr(0)),
  311.     "L;2": ("L;2", chr(2)+chr(0)),
  312.     "L;4": ("L;4", chr(4)+chr(0)),
  313.     "L":   ("L", chr(8)+chr(0)),
  314.     "I":   ("I;16B", chr(16)+chr(0)),
  315.     "P;1": ("P;1", chr(1)+chr(3)),
  316.     "P;2": ("P;2", chr(2)+chr(3)),
  317.     "P;4": ("P;4", chr(4)+chr(3)),
  318.     "P":   ("P", chr(8)+chr(3)),
  319.     "RGB": ("RGB", chr(8)+chr(2)),
  320.     "RGBA":("RGBA", chr(8)+chr(6)),
  321. }
  322.  
  323. def putchunk(fp, cid, *data):
  324.     "Write a PNG chunk (including CRC field)"
  325.  
  326.     data = string.join(data, "")
  327.  
  328.     fp.write(o32(len(data)) + cid)
  329.     fp.write(data)
  330.     hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
  331.     fp.write(o16(hi) + o16(lo))
  332.  
  333. class _idat:
  334.     # wrap output from the encoder in IDAT chunks
  335.  
  336.     def __init__(self, fp, chunk):
  337.     self.fp = fp
  338.     self.chunk = chunk
  339.     def write(self, data):
  340.     self.chunk(self.fp, "IDAT", data)
  341.  
  342. def _save(im, fp, filename, chunk=putchunk, check=0):
  343.     # save an image to disk (called by the save method)
  344.  
  345.     mode = im.mode
  346.  
  347.     if mode == "P":
  348.  
  349.     #
  350.     # attempt to minimize storage requirements for palette images
  351.  
  352.     if im.encoderinfo.has_key("bits"):
  353.  
  354.         # number of bits specified by user
  355.         n = 1 << im.encoderinfo["bits"]
  356.  
  357.     else:
  358.  
  359.         # check palette contents
  360.         n = 256 # FIXME
  361.  
  362.     if n <= 2:
  363.         bits = 1
  364.     elif n <= 4:
  365.         bits = 2
  366.     elif n <= 16:
  367.         bits = 4
  368.     else:
  369.         bits = 8
  370.  
  371.     if bits != 8:
  372.         mode = "%s;%d" % (mode, bits)
  373.  
  374.     # encoder options
  375.     if im.encoderinfo.has_key("dictionary"):
  376.     dictionary = im.encoderinfo["dictionary"]
  377.     else:
  378.     dictionary = ""
  379.  
  380.     im.encoderconfig = (im.encoderinfo.has_key("optimize"), dictionary)
  381.  
  382.     # get the corresponding PNG mode
  383.     try:
  384.     rawmode, mode = _OUTMODES[mode]
  385.     except KeyError:
  386.     raise IOError, "cannot write mode %s as PNG" % mode
  387.  
  388.     if check:
  389.     return check
  390.  
  391.     #
  392.     # write minimal PNG file
  393.  
  394.     fp.write(_MAGIC)
  395.  
  396.     chunk(fp, "IHDR",
  397.       o32(im.size[0]), o32(im.size[1]),    #  0: size
  398.       mode,                    #  8: depth/type
  399.       chr(0),                # 10: compression
  400.       chr(0),                # 11: filter category
  401.       chr(0))                # 12: interlace flag
  402.  
  403.     if im.mode == "P":
  404.     chunk(fp, "PLTE", im.im.getpalette("RGB"))
  405.  
  406.     if 0:
  407.     # FIXME: to be supported some day
  408.     chunk(fp, "gAMA", o32(int(gamma * 100000.0)))
  409.  
  410.     ImageFile._save(im, _idat(fp, chunk), [("zip", (0,0)+im.size, 0, rawmode)])
  411.  
  412.     chunk(fp, "IEND", "")
  413.  
  414.     try:
  415.         fp.flush()
  416.     except: pass
  417.  
  418.  
  419. # --------------------------------------------------------------------
  420. # PNG chunk converter
  421.  
  422. def getchunks(im, **params):
  423.     """Return a list of PNG chunks representing this image."""
  424.  
  425.     class collector:
  426.         data = []
  427.         def write(self, data):
  428.         pass
  429.     def append(self, chunk):
  430.         self.data.append(chunk)
  431.  
  432.     def append(fp, cid, *data):
  433.     data = string.join(data, "")
  434.     hi, lo = Image.core.crc32(data, Image.core.crc32(cid))
  435.     crc = o16(hi) + o16(lo)
  436.     fp.append((cid, data, crc))
  437.  
  438.     fp = collector()
  439.  
  440.     try:
  441.     im.encoderinfo = params
  442.     _save(im, fp, None, append)
  443.     finally:
  444.     del im.encoderinfo
  445.  
  446.     return fp.data
  447.  
  448.  
  449. # --------------------------------------------------------------------
  450. # Registry
  451.  
  452. Image.register_open("PNG", PngImageFile, _accept)
  453. Image.register_save("PNG", _save)
  454.  
  455. Image.register_extension("PNG", ".png")
  456.  
  457. Image.register_mime("PNG", "image/png")
  458.